/**
 * \file webrequest.c
 *       Filename:  webrequest.c
 * =====================================================================================
 * 
 * 
 *    Description:  Implementation of methods for HTTP web access. 
 * 
 *        Version:  1.0
 *        Created:  13/11/2009 00:12:50
 *       Revision:  none
 *       Compiler:  gcc
 * 
 *         Author:  François Hissel (fh), francois.hissel@m4x.org
 *        Company:  
 * 
 * =====================================================================================
 */

#include	<ctype.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<string.h>
#include	<fcntl.h>
#include	<errno.h>
#include	<netinet/in.h>
#include	<netdb.h>
#include	<sys/socket.h>
#include	<unistd.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<zlib.h>
#include	"webrequest.h"

char proxy[0x100];	//!< Name of proxy server 
int proxy_port;	//!< Port of proxy server

char* strnstr(char *buffer,size_t num,char *substring,int case_sensitive) {
	size_t len=strlen(substring);
	int index[len];
	int i=1;
	int j=0;
	index[0]=0;
	while (substring[i]!=0) {
		if (j>=0 && (substring[i]==substring[j] || (case_sensitive==0 && toupper(substring[i])==toupper(substring[j])))) index[i++]=++j; else if (j>0) j=index[j]-1; else {index[i++]=j=0;}
	}
	char *res=buffer;
	char *last=buffer+num;
	j=0;
	while (res+len-j<=last) {
		if (*res==substring[j] || (case_sensitive==0 && toupper(*res)==toupper(substring[j]))) {
			++res;++j;
			if (j==len) return res-j;
		} else {
			if (j>0) j=index[j-1]; else ++res;
		}
	}
	return 0;
}

unsigned long download(int fd,char *host,int port,char *path,char *headers) {
	char *phost;
	int pport;
	char *ppath;
	if (proxy[0]==0) {	// If a proxy is selected, use it in the request, else connect directly to the website
		phost=host;
		pport=port;
		ppath=(char*)malloc(strlen(path)+1);
		strcpy(ppath,path);
	} else {
		phost=proxy;
		pport=proxy_port;
		ppath=(char*)malloc(strlen(HTTP_PREFIX)+strlen(host)+strlen(path)+1);
		strcpy(ppath,HTTP_PREFIX);
		strcat(ppath,host);
		strcat(ppath,path);
	}
	int socket_fd;
	struct sockaddr_in name;
	struct hostent* hostinfo;	// Resolve address of proxy or website
	socket_fd=socket(PF_INET,SOCK_STREAM,0);
	name.sin_family=AF_INET;
	hostinfo=gethostbyname(phost);
	if (hostinfo==0) {
		fprintf(stderr,"Can't resolve address : %s\n",phost);
		free(ppath);
		return -1;
	}
	name.sin_addr=*((struct in_addr*)hostinfo->h_addr);
	name.sin_port=htons(pport);
	if (connect(socket_fd,(struct sockaddr*)(&name),sizeof(struct sockaddr_in))==-1) {	// Connect to server
		fprintf(stderr,"Can't connect to server : %s\n",phost);
		free(ppath);
		return -1;
	}
	// Building of request string
	unsigned char buffer[BUFFER_SIZE];
	ssize_t bsize;
	unsigned long size=0;
	sprintf((char*)(buffer),"GET %s HTTP/1.1\r\nHost: %s\r\n%s\r\n",ppath,host,headers);
//  sprintf((char*)buffer,"GET %s%s%s\nHost: %s\nAccept: */*\n",OSM_PREFIX,OSM_HOST,OSM_EXPORT,OSM_HOST);
//  	printf("%s\n",buffer);
	bsize=strlen((char*)buffer);
	if (write(socket_fd,buffer,bsize)<bsize) {	// Send request to server
		fprintf(stderr,"Couldn't send request to server : %s, error code %i.\n",host,errno);
		close(socket_fd);
		free(ppath);
		return -1;
	}
	// Read response from socket and uncompress it if necessary
	int readsize;	// Number of bytes read from site. At the end of the procedure, will be compared to the expected number which is given by datasize.
	char start=1;	// Boolean value to check if we are receiving the first block of data. If this is the case, we should read the header to find some description of the following content
	unsigned long int datasize=0;	// Expected number of bytes to be read for the content
	int ret;	// Return value of zlib procedures
	unsigned have;	// Amount of data feeded by inflate
	z_stream strm;	// Internal structure describing state of uncompression algorithm
	char encoded=0;	// Tells whether the stream is encoded in gzip format
	unsigned char *pos;	// Start of buffer to write
	int wsize;	// Number of bytes to write
	int error=0;
	unsigned char out_buffer[BUFFER_SIZE];
	do {
		readsize=0;
		bsize=read(socket_fd,buffer,BUFFER_SIZE);	// Read a block of data
		if (bsize==-1) {
			fprintf(stderr,"Error while reading from server %s, error code %i.\n",host,errno);
			close(socket_fd);
			free(ppath);
			return -1;
		} else if (bsize>0) {
			if (start && strncmp((char*)buffer,"HTTP",4)==0) {	// If it is the first block and if a header is detected, reads the header to find some description of the content (encoding and size)
				start=0;
				// Find length of content
				pos=(unsigned char*)strnstr((char*)buffer,bsize,"Content-Length",0);
				// Find if content is encoded using gzip
				if (pos!=0) datasize=atoi((char*)pos+16);
				pos=(unsigned char*)strnstr((char*)buffer,bsize,"Content-Encoding: gzip",0);
				if (pos!=0) {
					encoded=1;
					strm.zalloc=Z_NULL;
					strm.zfree=Z_NULL;
					strm.opaque=Z_NULL;
					strm.avail_in=0;
					strm.next_in=Z_NULL;
					ret=inflateInit2(&strm,0x1f);
				}
				pos=buffer;
				char emptyline=0;
				while (emptyline!=2 && pos<buffer+BUFFER_SIZE) {
					if (*pos=='\n') emptyline++;
					if (!isspace(*pos)) emptyline=0;
					++pos;
				}
				wsize=bsize-(pos-buffer);
			} else {
				pos=buffer;
				wsize=bsize;
			}
			readsize+=wsize;
			// Write data to file, uncompressing it if it is encoded
			if (encoded) {
				strm.avail_in=wsize;
				if (strm.avail_in==0) break;
				strm.next_in=pos;
				do {
					strm.avail_out=BUFFER_SIZE;
					strm.next_out=out_buffer;
					ret=inflate(&strm,Z_NO_FLUSH);
					have=BUFFER_SIZE-strm.avail_out;
					unsigned long wr=write(fd,out_buffer,have);
					if (wr==-1 || ret==Z_DATA_ERROR || ret==Z_MEM_ERROR) {
						inflateEnd(&strm);
						error=-1;
					}
				} while (error==0 && strm.avail_out==0);
			} else error=write(fd,pos,wsize);
			if (error==-1) {
				fprintf(stderr,"Error while writing file, error code %i.\n",errno);
				close(socket_fd);
				free(ppath);
				return -1;
			} else {
				size+=readsize;
			}
		}
	} while (bsize!=0);
	// Close open descriptors and exit
	if (encoded==1) inflateEnd(&strm);
	close(socket_fd);
	fsync(fd);
	free(ppath);
	if (datasize>0 && datasize!=size) {	// This may happen only if the headers of the page displayed a size different from the actual size read from the socket. The file is however saved on the disk, in order to allow manual fixing.
		fprintf(stderr,"Connection problem : file could not be fully downloaded. Expected size : %li bytes, actual size : %li bytes.\n",datasize,size);
		return -1;
	}
	return size;
}


